﻿using Microsoft.TeamFoundation.Controls;
using Microsoft.TeamFoundation.Framework.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
using Microsoft.VisualStudio.TeamFoundation.VersionControl;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Input;
using TeamExplorer.Merge.Base;

namespace TeamExplorer.Merge.MergeCandidates
{
    [TeamExplorerPageAttribute(MergeCandidatesDetailsPage.PageId)]
    public class MergeCandidatesDetailsPage : TeamExplorerBasePage
    {
        public const string PageId = "5C6C0420-D4AD-4136-A1CE-2ABC395A94AF";
        private const string CHECKIN_COMPLETE = "[Changeset {0}](Click to view the changeset details) successfully checked in.";        
        private const string POLICY_ERRORMESSAGE = "Changesets with policy failures are not allowed for auto merge.";
        private const string CONTINUE_CHECKIN = "Resolve all conflicts first, then [continue](click to checkin).";

        private VersionControlServer vcs;
        private TeamFoundationIdentity identity;
        private MergeCandidateDetails _candidateDetails;

        //checkin fields
        private bool _isCheckinOperationBusy = false;
        private int? _numPolicyFailures;
        private int checkedInChangesetId;

        //get operation fields
        private bool _isGetOperationBusy = false;
        private int? _numConflicts, _numFailures, _numWarnings;

        private bool _checkDisallowPolicyFailureMerges = false;

        //commands
        public ICommand ShowChangesetDetailsCommand { get; private set; }
        public ICommand ContinueCheckinCommand { get; private set; }

        public MergeCandidatesDetailsPage()
        {
            this.Title = "Merge Candidate Details";
            this.PageContent = new MergeCandidatesDetailsPageView();
            View.SetViewModel(this);
        }

        public override void Initialize(object sender, PageInitializeEventArgs e)
        {
            var _details = e.Context as MergeCandidateDetails;
            
            _candidateDetails = _details;

            RaisePropertyChanged("");
            base.Initialize(sender, e);
        }

        public override void Loaded(object sender, PageLoadedEventArgs e)
        {
            vcs = CurrentContext.TeamProjectCollection.GetService<VersionControlServer>();
            identity = CurrentContext.TeamProjectCollection.AuthorizedIdentity;
            SetupCommands();
            if (_candidateDetails != null)
                Merge();
            base.Loaded(sender, e);
        }

        private void SetupCommands()
        {
            ShowChangesetDetailsCommand = new DelegateCommand(ExecuteShowChangesetDetails);
            ContinueCheckinCommand = new DelegateCommand(ExecuteContinueCheckin);
        }

        public override void Refresh()
        {
            ClearNotifications();
            Merge();
            base.Refresh();
        }

        protected MergeCandidatesDetailsPageView View
        {
            get
            {
                return (MergeCandidatesDetailsPageView)PageContent;
            }
        }

        public string ChangesetId
        {
            get { return (_candidateDetails != null) ? _candidateDetails.ChangesetId.ToString() : String.Empty; }
        }

        public string SourceBranch
        {
            get
            {
                return _candidateDetails != null ? _candidateDetails.SourceBranch : String.Empty;
            }
        }

        public string TargetBranch
        {
            get
            {
                return _candidateDetails != null ? _candidateDetails.TargetBranch : String.Empty;
            }
        }

        private List<Failure> _failures;
        public List<Failure> Failures
        {
            get { return _failures; }
            set
            {
                if (value == _failures) { return; }

                _failures = value;
                RaisePropertyChanged("Failures");
            }
        }

        private List<PolicyFailureInfo> _policyFailures;
        public List<PolicyFailureInfo> PolicyFailures
        {
            get { return _policyFailures; }
            set
            {
                if (value == _policyFailures) { return; }

                _policyFailures = value;
                RaisePropertyChanged("PolicyFailures");
            }
        }

        public bool IsCheckinOperationBusy
        {
            get
            {
                return _isCheckinOperationBusy;
            }
            set
            {
                _isCheckinOperationBusy = value;
                RaisePropertyChanged("IsCheckinOperationBusy");
            }
        }

        public bool IsGetOperationBusy
        {
            get
            {
                return _isGetOperationBusy;
            }
            set
            {
                _isGetOperationBusy = value;
                RaisePropertyChanged("IsGetOperationBusy");
            }
        }

        public int? NumConflicts
        {
            get
            {
                return _numConflicts;
            }
            set
            {
                _numConflicts = value;
                RaisePropertyChanged("NumConflicts");
            }
        }

        public int? NumFailures
        {
            get
            {
                return _numFailures;
            }
            set
            {
                _numFailures = value;
                RaisePropertyChanged("NumFailures");
            }
        }

        public int? NumWarnings
        {
            get
            {
                return _numWarnings;
            }
            set
            {
                _numWarnings = value;
                RaisePropertyChanged("NumWarnings");
            }
        }

        public int? NumPolicyFailures
        {
            get
            {
                return _numPolicyFailures;
            }
            set
            {
                _numPolicyFailures = value;
                RaisePropertyChanged("NumPolicyFailures");
            }
        }

        private async void Merge()
        {
            SetGetOperationIndicators();
            var mergeGetTask = Task.Run(() => { return GetOperation(_candidateDetails.ChangesetId, _candidateDetails.SourceBranch, _candidateDetails.TargetBranch, _candidateDetails.Action); });
            await mergeGetTask;

            GetOperationCompleted(mergeGetTask.Result);
            switch (ParseGetOperationStatus(mergeGetTask.Result))
            {
                case GetStatusAction.NoAction:
                    ShowNotification("No action required. Target is up to date with source files. Go back to the list.", NotificationType.Information);
                    break;
                case GetStatusAction.ResolveConflicts:
                    ResolveConflicts();
                    break;
                case GetStatusAction.ErrorOut:
                    Failures = mergeGetTask.Result.Item1.GetFailures().ToList();
                    break;
                case GetStatusAction.ProceedWithCheckin:
                    ProceedWithCheckin(false);
                    break;
                default:
                    return;
            }
        }
        
        private void ResolveConflicts()
        {
            InvokeResolveConflictsPage(_candidateDetails);
            ShowNotification(CONTINUE_CHECKIN, NotificationType.Warning, ContinueCheckinCommand);
        }
        
        private async void ProceedWithCheckin(bool checkConflicts)
        {
            ClearNotifications();
            
            if (checkConflicts)
            {
                NumConflicts = _candidateDetails.CurrentWorkspace.QueryConflicts(new string[] { _candidateDetails.TargetBranch }, true).Length;
                if (NumConflicts > 0)
                {
                    ResolveConflicts();
                    return;
                }
            }

            SetCheckinOperationIndicators();
            var checkInTask = Task.Run<CheckinResult>(() => { return CheckinOperation(); });
            CheckinResult checkInResult = await checkInTask;
            CheckinOperationCompleted(checkInResult);
        }
                
        private void SetGetOperationIndicators()
        {
            IsBusy = true;
            IsGetOperationBusy = true;
        }

        private Tuple<GetStatus, Conflict[]> GetOperation(int changesetId, string sourceBranch, string targetBranch, MergeAction action)
        {
            GetStatus result = null;

            VersionSpec changesetSpec = new ChangesetVersionSpec(changesetId);

            //on checkin instead of get, evaluate checkin, if conflicts, do same operation as 'Resolve conflicts(x)', and continuewith checkin
            //new task: Evaluation checkin after getresult (ignore checking result, only on failure, stop)
            //on Evaulation checkin, do resolve conflicts if conflicts exist from evaluationcheckin call (popup, blocking)
            //continuewith CheckinTask
            MergeOptions options = (action == MergeAction.Discard)? MergeOptions.AlwaysAcceptMine : MergeOptions.None;

            result = _candidateDetails.CurrentWorkspace.Merge(sourceBranch, targetBranch, changesetSpec, changesetSpec, LockLevel.Unchanged, RecursionType.Full, options);
            Conflict[] conflicts = _candidateDetails.CurrentWorkspace.QueryConflicts(new string[] { targetBranch }, true);

            return new Tuple<GetStatus, Conflict[]>(result, conflicts);
        }

        private void GetOperationCompleted(Tuple<GetStatus, Conflict[]> result)
        {
            ClearGetIndicators();

            GetStatus status = result.Item1;

            NumWarnings = status.NumWarnings;
            NumConflicts = Math.Max(status.NumConflicts, result.Item2.Where(cf => !cf.IsResolved).Count());
            NumFailures = status.NumFailures;
        }

        private GetStatusAction ParseGetOperationStatus(Tuple<GetStatus, Conflict[]> result)
        {
            GetStatus status = result.Item1;

            if (status.NumFailures > 0)
                return GetStatusAction.ErrorOut;

            if (status.NoActionNeeded && result.Item2.Length == 0)
                return GetStatusAction.NoAction;

            if (!status.NoActionNeeded 
                && (status.NumOperations > 0 || (status.NumOperations == 0 && status.NumResolvedConflicts > 0)) 
                && !status.HaveResolvableWarnings 
                && Math.Max(status.NumConflicts, result.Item2.Length) == 0)
                return GetStatusAction.ProceedWithCheckin;
                        
            return GetStatusAction.ResolveConflicts;
        }

        private void InvokeResolveConflictsPage(MergeCandidateDetails details)
        {
            Assembly versionControlAssembly = Assembly.GetAssembly(typeof(VersionControlExt));
            if (versionControlAssembly == null) return;

            Type rcMgr = versionControlAssembly.GetType("Microsoft.VisualStudio.TeamFoundation.VersionControl.ResolveConflictsManager");
            if (rcMgr == null) return;

            BindingFlags flags = BindingFlags.Public | BindingFlags.Instance;
            MethodInfo mi = rcMgr.GetMethod("Initialize", flags);
            object instantiatedType = Activator.CreateInstance(rcMgr, flags, null, null, null);
            mi.Invoke(instantiatedType, null);

            MethodInfo resolveConflictsMethod = rcMgr.GetMethod("ResolveConflicts", BindingFlags.NonPublic | BindingFlags.Instance);
            resolveConflictsMethod.Invoke(instantiatedType,
                new object[] { details.CurrentWorkspace, new string[] { details.TargetBranch }, true, false });
        }

        private void ClearGetIndicators()
        {
            IsBusy = false;
            IsGetOperationBusy = false;
        }

        private void SetCheckinOperationIndicators()
        {
            IsBusy = true;
            IsCheckinOperationBusy = true;
        }

        private CheckinResult CheckinOperation()
        {
            int newChangesetId;
            WorkItemCheckinInfo[] workItemInfosArray = null;

            PendingChange[] changes = _candidateDetails.CurrentWorkspace.GetPendingChanges();
            var mergeChanges = changes.Where(m => m.IsMerge).ToArray();

            var changeset = vcs.GetChangeset(_candidateDetails.ChangesetId);
            if (changeset == null) { throw new Exception(String.Format("Changeset {0} could not be retrieved from source control", _candidateDetails.ChangesetId)); }

            var workItems = changeset.WorkItems;

            NumPolicyFailures = changeset.PolicyOverride.PolicyFailures.Count();
            if (_checkDisallowPolicyFailureMerges && changeset.PolicyOverride.PolicyFailures.Any())
            {
                PolicyFailures = changeset.PolicyOverride.PolicyFailures.ToList();
                return new CheckinResult { Succeeded = false, ErrorString = POLICY_ERRORMESSAGE };
            }

            if (workItems.Any())
            {
                var workItemInfos = from workItem in workItems
                                    select new WorkItemCheckinInfo(workItem, WorkItemCheckinAction.Associate);
                workItemInfosArray = workItemInfos.ToArray();
            }

            try
            {
                newChangesetId = _candidateDetails.CurrentWorkspace.CheckIn(mergeChanges, String.Format("Merge {0}: {1}", _candidateDetails.ChangesetId, changeset.Comment), changeset.CheckinNote, workItemInfosArray, changeset.PolicyOverride);
            }
            catch (CheckinException cie)
            {
                return new CheckinResult { Succeeded = false, ErrorString = cie.Message };
            }
            return new CheckinResult { Succeeded = true, ChangesetId = newChangesetId };
        }

        private void CheckinOperationCompleted(CheckinResult result)
        {
            IsBusy = false;
            IsCheckinOperationBusy = false;

            if (result.Succeeded)
            {
                checkedInChangesetId = result.ChangesetId;
                ShowNotification(String.Format(CHECKIN_COMPLETE, checkedInChangesetId), NotificationType.Information, ShowChangesetDetailsCommand);
            }
            else
            {
                ShowNotification(result.ErrorString, NotificationType.Error);
            }
        }

        private void ExecuteShowChangesetDetails(object param)
        {
            NavigateToChangesetDetails(checkedInChangesetId);
        }
        
        private void ExecuteContinueCheckin(object obj)
        {
            ProceedWithCheckin(true);
        }

        private void NavigateToChangesetDetails(int changesetId)
        {
            try
            {
                var teamExplorer = GetService<ITeamExplorer>();
                teamExplorer.NavigateToPage(PageIds.ChangesetDetailsPageId, changesetId);
            }
            catch (Exception ex)
            {
                //log
            }
        }
    }
}
